-- SkyExploreM
-- created by panyl
-- 空战探索模块

module("SkyExploreM", package.seeall);

local RESET_RADAR_INTERNAL = 86400  -- 24小时
local ENERGY_AUTO_RECOVER  = 1800;  -- 30分钟

local exploreEvents = {};
local explroeResetInfo = {};
local requireTimesInfo = {};
local fixedPos = {};
local buffIds = {};
local maxRequireTimes = 1;
local rules = {};

local calcFixedPos;

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    exploreEvents = {};

    -- 转换下sky_explore_event_CSV信息表
    local sky_explore_event_CSV = get_sky_explore_event_CSV();
    for i = 1, #sky_explore_event_CSV do
        local row = sky_explore_event_CSV[i];
        exploreEvents[row.id] = row;

        local requireTimes = row["require_times"];
        if  requireTimes ~= "" and requireTimes ~= nil then
            requireTimesInfo[requireTimes] = row.id;
            -- 获得当前最大次数配置
            if maxRequireTimes < requireTimes then
                maxRequireTimes = requireTimes;
            end
        end

        local fixedPosInfo = row["fixed_pos"];
        if "table" == type(fixedPosInfo) then
            calcFixedPos(row.id, fixedPosInfo);
        end

        -- 固定浮岛出现状态的buffer记录位置
        local bufferId = row["buffer_id"];
        if "number" == type(bufferId) and bufferId > 0 then
            buffIds[bufferId] = row.id;
        end
    end

    -- 转换下雷达重置配置表信息
    local sky_explore_reset_CSV = get_sky_explore_reset_CSV();
    for i = 1, #sky_explore_reset_CSV do
        local row = sky_explore_reset_CSV[i];
        explroeResetInfo[row.id] = row;
    end
end

-- 模块初始化操作
function init()
    if not _init then
        loadCsv();

        -- 载入所有的规则处理子模块
        rules = LOAD_PATH("game/logic/module/sky_explore");
    end

    EventMgr.removeAll("SkyExploreM");
end

function query(id, path)
    local m = exploreEvents[id];

    if not m or not path then
        return m;
    end

    return m[path];
end

function queryReset(id, path)
    local m = explroeResetInfo[id];

    if not m or not path then
        return m;
    end

    return m[path];
end

-- 通知探索结果
function notifyExploreResult(index, result)
    local skyExplore = ME.user.dbase:query("sky_explore", {});
    local para = skyExplore[index];
    local id = para["id"];

    local rule = query(id, "rule")
    local mod = rules[rule];
    if not mod then
        return;
    end

    -- TODO: 奖励格式需要转下
    local bonus;
    if result.bonus then
        local temp = result.bonus;
        if type(temp[1]) == "number" then
            bonus = { temp, };
        else
            bonus = {};
            for index, arr in pairs(temp) do
                bonus[index] = arr;
            end
        end
    end
    result.bonus = bonus;

    -- 交给子模块处理
    mod.handleResult(index, result);

    if result.trigger_id then
        -- 触发了特殊事件
        para.trigger_id = result.trigger_id;
        para.question_id = result.question_id;
        para.goods = result.goods;
        para.ship_id = result.ship_id;
        para.cost = result.cost;

        para.fail_times = 0;
        para.last_fail = 0;
    elseif result.result == 1 then
        -- 如果成功了，就把数据清掉
        skyExplore[index] = nil;
    elseif result.result == "find_treasure" then
        -- 发现宝藏了
        para.treasure_id = result.treasure_id;
    elseif result.result == rule then
        -- 成功了，但是需要保留数据
        -- 暂时什么也不做
    else
        if result.fail_times then
            -- 失败次数+1
            para.fail_times = result.fail_times;
        end

        if result.last_fail then
            para.last_fail = result.last_fail;
        end
    end

    EventMgr.fire(event.SKY_CITY_EXPLORE_RESULT, { ["result"] = result, ["pos"] = para["pos"] });
end

-- 奖励
function doBonus(bonus, treasureInfo, index)
    local data = bonus;

    EventMgr.fire(event.SKY_EXPLORE_BONUS, {["bonus"] = data, ["treasureInfo"] = treasureInfo, ["index"] = index});
end

-- 最大探索数量
function maxExploreTimes()
    return FormulaM.invoke("CALC_MAX_SHIP_TARGET_NUM", ME.user);
end

-- 能否探索
function canExplore()
    -- 探索次数用完了
    local times = calcExploreTimes();
    if times >= maxExploreTimes() then
        trace("SkyExploreM", "雷达点已满");
        return "max_explore";
    end

    -- 消耗不足
    if ME.user:queryAttrib("ship_energy") <= 0 then
        trace("SkyExploreM", "能量不足，不能发起探索。");
        return "no_energy";
    end

    -- 如果还未出战宠物
    --[[if #SkyCombatM.getSkyArmy() <= 0 then
        trace("SkyCombatM", "还未出战宠物。");
        return "no_air_army";
    end]]

    return true;
end

-- 探索
function doExplore()
    -- 如果不能发起探索
    if canExplore() ~= true then
        print("无法发起探索");
        return false;
    end

    -- 当前需要指引，需要等待服务器返回
    if needWait() then
        print("当前需要指引，需要等待服务器返回");
        return false;
    end

    -- 客户端先不做消耗

    -- 直接向服务器发起
    Communicate.send("CMD_SKY_EXPLORE", {});
end

-- 事件消耗
function calcExploreCost(index)
    local eventInfo = getExploreEvents()[index];

    -- 公式计算吧
    return FormulaM.invoke("CACL_SKY_EVENT_COST", eventInfo);
end

-- 判断能发发起事件，就看消耗
function canDoEvent(index)
    local cost = calcExploreCost(index);
    if not cost then
        return true;
    end

    -- 判断消耗是否足够
    for classId, count in pairs(cost) do
        if type(classId) == "string" then
            if ME.user:queryAttrib(classId) < count then
                return "money_lack";
            end
        else
            if ItemM.getAmount(ME.user, classId) < count then
                return "item_lack";
            end
        end
    end

    return true;
end

-- 袭击（登陆等）
function doEvent(index, extraData)
    local ret = canDoEvent(index)
    if true ~= ret then
        return ret;
    end

    -- 直接发起请求
    Communicate.send("CMD_SKY_EXPLORE_EVENT", {["index"] = index, ["extra_data"] = extraData or {}});

    return true;
end

-- 当前所有已探索到的浮岛
function getExploreEvents()
    return ME.user.dbase:query("sky_explore", {});
end

-- 重置剩余冷却时间
function leftResetTime()
    local resetTime = ME.user.dbase:query("reset_radar_time", 0);
    local curTime = TimeM.getServerTime();

    if resetTime <= 0 or not isSameDay(resetTime, curTime, TimeM.getTimeOffsetDifference()) then
        return 0;
    end

    -- 计算服务器明天0点的时间戳
    local refreshTime = TimeM.calcZeroTime() + SECONDS_PER_DAY;

    return refreshTime - curTime;
end

-- 判断是否可以重置
function canResetExplore(id)
    if leftResetTime() > 0 then
        -- 未冷却
        print("雷达重置时间未冷却，无法重置雷达");
        return false;
    end

    local resetInfo = queryReset(id);
    local cost = resetInfo["cost"];

    local exploreLevel = ME.user.dbase:query("explore_level", 0);

    if exploreLevel < resetInfo["level_request"] then
        print("玩家的探索等级不足，无法重置雷达");
        return false;
    end

    if not ME.user:canCost(cost) then
        print("玩家不足以支付消耗，无法重置");
        return false;
    end

    return true;
end

-- 重置
function resetExplore(id, isDirect)
    if not canResetExplore(id) then
        -- 还不能重置
        return false;
    end

    -- 先扣除
    local cost = queryReset(id, "cost");
    for attrib, value in pairs(cost) do
        ME.user:costAttrib(attrib, value);
    end

    -- 直接向服务器发起
    SyncM.addMessage("CMD_RESET_RADAR", {["id"] = id, ["is_direct"] = isDirect or 0});
    SyncM.startSync();

    return true;
end

-- 计算挑战失败次数
function calcFailTimes(para)
    if not para["fail_times"] then
        return 0;
    end

    -- 上次挑战失败时间
    local lastFailTime = para["last_fail"] or 0;
    local curTime = TimeM.getServerTime();
    local year = os.date("%Y", curTime);
    local mon  = os.date("%m", curTime);
    local day  = os.date("%d", curTime);

    -- 如果不是同一天
    if not isSameDay(lastFailTime, curTime) then
        return 0;
    end

    -- 以中午12:00为分界线
    local divideTime = os.time({year = year, month = mon, day = day, hour = 12});
    if lastFailTime < divideTime and curTime >= divideTime then
        return 0;
    end

    return para["fail_times"] or 0;
end

-- 挑战失败重置时间
function getFailResetTime(para)
    -- 上次挑战失败时间
    local lastFailTime = para["last_fail"] or 0;
    local curTime = TimeM.getServerTime();
    local year = os.date("%Y", curTime);
    local mon  = os.date("%m", curTime);
    local day  = os.date("%d", curTime);

    -- 如果不是同一天
    if not isSameDay(lastFailTime, curTime) then
        return 0;
    end

    -- 以中午12:00为分界线
    local divideTime = os.time({year = year, month = mon, day = day, hour = 12});
    local divideTime2 = divideTime + 12 * 3600;
    if lastFailTime < divideTime and curTime >= divideTime then
        return 0;
    end

    if divideTime > curTime then
        -- 今天12点重置
        return divideTime - curTime;
    end
    -- 零点重置
    return divideTime2 - curTime;
end

-- 计算位置
function calcEventPos(index)
    local eventInfo = getExploreEvents()[index];

    if not eventInfo then
        return;
    end

    return parsePos(eventInfo.pos);
end

-- 把pos解析成xy
function parsePos(pos)
    -- // 四个方向
    -- array direction = ({ 1<<16, 2<<16, 3<<16, 4<<16, });
    -- int   x = random(1, size), y = random(1, size);
    -- int   pos = direction + x * 200 + y;
    local direction = bit.brshift(pos, 16);
    local pos = bit.band(pos, 0xffff);

    local y = pos % 200;
    local x = math.modf(pos / 200);
    local ret;

    if direction == 1 then
        -- 第一象限
        ret = {["x"] = x, ["y"] = y, };
    elseif direction == 2 then
        -- 第二象限
        ret = {["x"] = -x, ["y"] = y, };
    elseif direction == 3 then
        -- 第三象限
        ret = {["x"] = -x, ["y"] = -y, };
    else
        -- 第四象限
        ret = {["x"] = x, ["y"] = -y, };
    end

    return ret;
end

-- 根据xy计算pos
function calcPosByXY(x, y)
    local direction = 0;

    if x > 0 and y > 0 then
        direction = 1;
    elseif x < 0 and y > 0 then
        direction = 2;
    elseif x < 0 and y < 0 then
        direction = 3;
    else
        direction = 4;
    end

    return bit.blshift(direction, 16) + math.abs(x) * 200 + math.abs(y);
end

-- 计算固定位置
calcFixedPos = function(id, posInf)
    local direction = posInf["pos_d"];
    local x = posInf["pos_x"];
    local y = posInf["pos_y"];

    if direction == nil or x == nil or nil == y then
        return;
    end

    local pos = bit.blshift(direction, 16) + math.abs(x) * 200 + math.abs(y);
    fixedPos[id] = pos;
end

-- 计算当前雷达次数（减去固定浮岛）
function calcExploreTimes()
    local skyExplore = ME.user.dbase:query("sky_explore", {});
    local times = 0;

    for _, value in pairs(skyExplore) do
        if query(value["id"], "forever") ~= 1 then
            times = times + 1;
        end
    end

    return times;
end

-- 判断是否需要指引
function getIsGuide(requireTimes)
    local eventId = requireTimesInfo[requireTimes];

    local isGuide = false;
    if eventId ~= nil then
        local guide = query(eventId, "is_guide");

        if guide == 1 then
            isGuide = true;
        end
    end

    return isGuide;
end

-- 判断当前是否需要等待
function needWait()
    local clientTimes = ME.user.dbase:queryTemp("client_times");
    local requireTimes = ME.user.dbase:query("sky_explore_times", 0);

    -- 当前次数大于最大固定次数，不需要再等待引导
    if maxRequireTimes < requireTimes then
        return false;
    end

    if clientTimes == nil then
        clientTimes = requireTimes;
    end

    if 1 == ME.user.dbase:queryTemp("explore_need_guide", 0) then
        return true;
    end

    clientTimes = clientTimes + 1;
    if getIsGuide(clientTimes) then
        EventMgr.fire(event.SC_EXPLORE_WAIT_GUIDE);
        ME.user.dbase:setTemp("explore_need_guide", 1);
    end

    -- 客户端次数+1
    ME.user.dbase:setTemp("client_times", clientTimes);

    return false;
end

-- 获取探索等级
function getExploreLevel()
    return ME.user.dbase:query("explore_level", 0);
end

-- 获取总探索次数
function getTotalExploreTimes()
    return ME.user.dbase:query("sky_explore_times", 0);
end

-- 根据规则获取出现在雷达上的元素
function findExploredIdsByRule(rule)
    local skyExplore = ME.user.dbase:query("sky_explore", {});
    local list = table.values(skyExplore);
    if #list == 0 then
        return {};
    end

    local ret = {};
    for _, brief in pairs(list) do
        local id = brief.id;
        if query(id, "rule") == rule then
            table.insert(ret, id);
        end
    end
    return ret;
end

-- 获得固定岛屿信息
function getForeverIslandData()
    local foreverIsland = BuffDataM.query("forever_island");
    local ret = {};
    for bufferId, state in pairs(foreverIsland) do
        if state > 0 and buffIds[bufferId] then
            local id = buffIds[bufferId];
            if id ~= nil then
                local data = {["id"] = id, ["explore_level"] = 0, ["pos"] = fixedPos[id]};
                table.insert(ret, data);
            end
        end
    end

    return ret;
end

-- 雷达能量恢复满的时间
function getRaderMaxTime()
    local maxEnergy = FormulaM.invoke("CALC_MAX_SHIP_ENERGY", ME.user);
    local curEnergy = ME.user.dbase:query("ship_energy", 0);

    local difEnergy = maxEnergy - curEnergy;
    if difEnergy <= 0 then
        return 0;
    end

    -- 属性降低恢复时间
    local prop = PropM.combine(ME.user, "reduce_sky_time", "recover_energy");
    local energyRecoverTime = PropM.apply(prop, ENERGY_AUTO_RECOVER);

    local tempTime = difEnergy * energyRecoverTime;
    local difTime = TimeM.getServerTime() - ME.user.dbase:query("ship_energy_recover");
    if difTime <= 0 then
        return 0;
    end

    return tempTime - difTime;
end
